/*
 * MainView

@Override
public String name()
{
throw new UnsupportedOperationException("Not supported yet.");
}

@Override
public String enabledProperty()
{
throw new UnsupportedOperationException("Not supported yet.");
}

@Override
public String selectedProperty()
{
throw new UnsupportedOperationException("Not supported yet.");
}

@Override
public BlockingScope block()
{
throw new UnsupportedOperationException("Not supported yet.");
}

@Override
public Class<? extends Annotation> annotationType()
{
throw new UnsupportedOperationException("Not supported yet.");
}
}java
 */
package azure;

import java.awt.Color;
import java.awt.Component;
import java.awt.Desktop;
import java.awt.Dimension;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.awt.image.BufferedImage;
import org.jdesktop.application.Action;
import org.jdesktop.application.SingleFrameApplication;
import org.jdesktop.application.FrameView;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JTree;
import javax.swing.ToolTipManager;
import javax.swing.filechooser.FileSystemView;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;

/**
 * The application's main frame.
 */
public class MainView extends FrameView
{
    //<editor-fold defaultstate="collapsed" desc="Global Variables">

    private WindowListener mainWindowListener = new WindowListener()
    {

        @Override
        public void windowOpened(WindowEvent e)
        {
        }

        @Override
        public void windowClosing(WindowEvent e)
        {
            saveSettings();
        }

        @Override
        public void windowClosed(WindowEvent e)
        {
        }

        @Override
        public void windowIconified(WindowEvent e)
        {
        }

        @Override
        public void windowDeiconified(WindowEvent e)
        {
        }

        @Override
        public void windowActivated(WindowEvent e)
        {
        }

        @Override
        public void windowDeactivated(WindowEvent e)
        {
        }
    };
    private FileFilter azureImageFileFilter = new FileFilter()
    {

        @Override
        public boolean accept(File inputFile)
        {
            if (inputFile.isFile())
            {
                for (String extension : imageExtensions)
                {
                    if (inputFile.getName().toLowerCase().endsWith(extension))
                    {
                        return true;
                    }
                }
            }
            return false;
        }
    };
    private FileFilter directoryOnlyFilter = new FileFilter()
    {

        @Override
        public boolean accept(File pathname)
        {
            return pathname.isDirectory();
        }
    };
    private final String[] imageExtensions = new String[]
    {
        "jpg", "jpeg", "gif", "png"
    };
    private final String LIBNAME = "library.sqlite";
    private ArrayList<Manga> library;
    private SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd");
    private MutableTreeNode rootNode = new DefaultMutableTreeNode("Library");
    private DefaultTreeModel newModel = new DefaultTreeModel(rootNode);
    public TreeCellRenderer renderer = new DefaultTreeCellRenderer()
    {

        @Override
        public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus)
        {

            super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
            if (leaf && isPage(value))
            {
                DefaultMutableTreeNode node = (DefaultMutableTreeNode)value;
                Page p = (Page)node.getUserObject();
                if (p.lastReadDate != null)
                {
                    setForeground(Color.green);
                    setToolTipText("Last read: " + dateFormatter.format(p.lastReadDate));
                }
            }
            else
            {
                setToolTipText(null); //no tool tip
            }

            return this;
        }

        protected boolean isPage(Object value)
        {
            DefaultMutableTreeNode node = (DefaultMutableTreeNode)value;
            return (node.getUserObject() instanceof Page);
        }
    };
    private AppSettings appSettings = new AppSettings();
    private DefaultMutableTreeNode activeNode; //Node object for navigation
    private Image activeImage;
    private Dimension activeImageSize;
    /*Key Codes*/
    private final int RETURN_KEY = 10, ESCAPE_KEY = 27, SPACE_KEY = 32, LEFT_KEY = 37, UP_KEY = 38, RIGHT_KEY = 39, DOWN_KEY = 40, BACKSPACE_KEY = 8;
    /*Tab Pages*/
    private final int VIEWER_TAB = 0, LIBRARY_TAB = 1;
    private final Dimension MINIMUM_SIZE = new Dimension(640, 480);
    //</editor-fold>

    public MainView(SingleFrameApplication app)
    {
        super(app);
        try
        {
            initComponents();
            loadAll();
            super.getFrame().setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            super.getFrame().addWindowListener(mainWindowListener);
            jpButtonBar.addKeyListener(new KeyListener()
            {

                @Override
                public void keyTyped(KeyEvent e)
                {
                    System.out.println("Keycode: " + e.getKeyCode());
                }

                @Override
                public void keyPressed(KeyEvent e)
                {
                    System.out.println("Keycode: " + e.getKeyCode());
                }

                @Override
                public void keyReleased(KeyEvent e)
                {
                    System.out.println("Keycode: " + e.getKeyCode());
                }
            });
            jtLibraryTree.addKeyListener(new KeyListener()
            {

                @Override
                public void keyTyped(KeyEvent e)
                {
                }

                @Override
                /**
                 * Handle a press of the enter key on a TreeNode, which will do the same as double-clicking the page
                 */
                public void keyPressed(KeyEvent e)
                {
                    switch (e.getKeyCode())
                    {
                        case RETURN_KEY: //Handle an Enter/Return keypress
                        {
                            TreePath path = jtLibraryTree.getSelectionPath();
                            if (path != null)
                            {
                                handleNodeAction(path);
                            }
                            break;
                        }
                        case BACKSPACE_KEY:
                        {
                            deleteSelectedItem();
                            break;
                        }
                    }
                }

                @Override
                public void keyReleased(KeyEvent e)
                {
                }
            });
            ToolTipManager.sharedInstance().registerComponent(jtLibraryTree);
            if (appSettings.autoResume)
            {
                resume();
            }
            jpButtonBar.requestFocus();
        }
        catch (Exception e)
        {
            ErrorLogger.logErrorMessage("MainView", 288, e.toString());
        }
    }

    /**
     * On startup, load both the application settings and the library
     */
    @Action
    private void loadAll()
    {
        if (libraryExists())
        {
            SQLibrarian lib = new SQLibrarian();
            loadSettings(lib);
            loadLibrary(lib);
            lib.closeConn();
        }
        else
        {
            if (JOptionPane.showConfirmDialog(this.getComponent(), "Azure was unable to locate its database. Would you like to create a new one?\nIf this is Azure's first run, click Yes", "Database File Not Found", JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION)
            {
                SQLibrarian lib = new SQLibrarian();
                if (lib.createDatabase())
                {
                    JOptionPane.showMessageDialog(this.getComponent(), "Database created successfully");
                    appSettings.language = AppSettings.Language.DEFAULT;
                }
            }
        }
        if (errorLogExists())
        {
            Desktop desktop = Desktop.getDesktop();
            if (desktop.isSupported(Desktop.Action.MAIL))
            {
                if (JOptionPane.showConfirmDialog(this.getFrame(), "Looks like Azure encountered a problem the last time it was run."
                        + "\nA log file has been created. If you have a mail client set up, you can email this straight to the developer."
                        + "\nWould you like to do this?", "Errors Have Occured", JOptionPane.YES_NO_OPTION, JOptionPane.INFORMATION_MESSAGE) == JOptionPane.YES_OPTION)
                {

                    URI uriMailTo = null;
                    try
                    {
                        uriMailTo = new URI("mailto", "quantumparticleresearch@gmail.com?SUBJECT=Azure Error Report&BODY=" + ErrorLogger.loadErrorLog(), null);
                        desktop.mail(uriMailTo);
                    }
                    catch (IOException ioe)
                    {
                        ioe.printStackTrace();
                    }
                    catch (URISyntaxException use)
                    {
                        use.printStackTrace();
                    }
                    File logfile = new File(ErrorLogger.LOGFILE);
                    logfile.delete();
                }
            }
            else
            {
                JOptionPane.showMessageDialog(this.getFrame(), "Looks like Azure encountered a problem the last time it was run."
                        + "\nA log file has been created in Azure's working directory. If you could email this to the developer at quantumparticleresearch@gmail.com,"
                        + "\nit would really help future versions of Azure. Thanks.", "Errors Have Occured", JOptionPane.INFORMATION_MESSAGE);
            }
        }
    }

    private void saveSettings()
    {
        if (libraryExists())
        {
            SQLibrarian lib = new SQLibrarian();
            if (library.size() > 0)
            {
                lib.saveLibraryToDB(library);
            }
            lib.saveSettingsToDB(appSettings);
            lib.closeConn();
        }
        else
        {
            JOptionPane.showMessageDialog(this.getFrame(), "Warning: Database file could not be located!\nPlease check your Azure directory for a file named " + LIBNAME + ". If the file exists, please ensure you have write access.\nIf the library does not exst, please re-run Azure and the file will be created.", "Database File Not Found", JOptionPane.ERROR_MESSAGE);
        }
    }

    @Action
    private void loadSettings(SQLibrarian lib)
    {
        appSettings = lib.loadSettings();
        if ((appSettings.height > 0) && (appSettings.width > 0) && (appSettings.x >= 0) && (appSettings.y >= 0))
        {
            super.getFrame().setBounds(appSettings.x, appSettings.y, appSettings.width, appSettings.height);
        }
        super.getFrame().pack();
    }

    @Action
    private void loadLibrary(SQLibrarian lib)
    {
        library = lib.loadFromDB();
        displayMangaLibrary();
    }

    private void logWindowBounds()
    {
        Rectangle bounds = super.getFrame().getBounds();
        if (appSettings != null)
        {
            appSettings.x = bounds.x;
            appSettings.y = bounds.y;
            appSettings.width = bounds.width;
            appSettings.height = bounds.height;
            appSettings.imageLabelX = jlImage.getWidth();
            appSettings.imageLabelY = jlImage.getHeight();
        }
    }

    @Action
    public void resume()
    { //1
        if (appSettings.lastOpenMangaID > 0)
        {//2
            //TODO: This is a quick fix. somewhere along the line, the JLabel is being set to the wrong size, despite the use of a layout
            //manager. This gives an annoying bug where the panel is stretched out. If the label is properly sized beforehand, the panel doesn't stretch
            if ((appSettings.imageLabelX > 0) && (appSettings.imageLabelY > 0))
            {//3
                jlImage.setSize(appSettings.imageLabelX, appSettings.imageLabelY);
            }//3
            for (int a = 0; a < rootNode.getChildCount(); a += 1)
            {//4
                DefaultMutableTreeNode mangaNode = (DefaultMutableTreeNode)rootNode.getChildAt(a);
                if (((Manga)mangaNode.getUserObject()).libraryID == appSettings.lastOpenMangaID)
                {//5
                    for (int b = 0; b < mangaNode.getChildCount(); b += 1)
                    {//6
                        DefaultMutableTreeNode volumeNode = (DefaultMutableTreeNode)mangaNode.getChildAt(b);
                        if (((Volume)volumeNode.getUserObject()).volumeID == appSettings.lastOpenVolumeID)
                        {//7
                            for (int c = 0; c < volumeNode.getChildCount(); c += 1)
                            {//8
                                DefaultMutableTreeNode chapterNode = (DefaultMutableTreeNode)volumeNode.getChildAt(c);
                                if (((Chapter)chapterNode.getUserObject()).chapterID == appSettings.lastOpenChapterID)
                                {//9
                                    for (int d = 0; d < chapterNode.getChildCount(); d += 1)
                                    {//10
                                        DefaultMutableTreeNode pageNode = (DefaultMutableTreeNode)chapterNode.getChildAt(d);
                                        if (((Page)pageNode.getUserObject()).pageID == appSettings.lastOpenPageID)
                                        {//11
                                            navigateToNode(pageNode);
                                            break; //Break the Page loop
                                        }//11
                                    }//10
                                    break; //Break the Chapter loop
                                }//9
                            }//8
                            break; //Break the Volume loop
                        }//7
                    }//6
                    break; //Break the Manga loop
                }//5
            }//4
        }//2
    }//1

    /**
     * To clear and repopulate the library, it is required to create a whole new TreeModel, since clearing the existing
     * is nearly impossible
     **/
    private void displayMangaLibrary()
    {
        if (rootNode.getChildCount() > 0) //Clear the library Tree if it's populated
        {
            for (int x = 0; x < rootNode.getChildCount(); x += 1)
            {
                rootNode.remove(x);
            }
        }
        newModel = new DefaultTreeModel(rootNode);
        for (Manga m : library) //For-each loop
        {
            DefaultMutableTreeNode manga = new DefaultMutableTreeNode(m);
            for (Volume v : m.volumes)
            {
                DefaultMutableTreeNode volume = new DefaultMutableTreeNode(v);
                for (Chapter c : v.chapters)
                {
                    DefaultMutableTreeNode chapter = new DefaultMutableTreeNode(c);
                    for (Page p : c.pages)
                    {
                        DefaultMutableTreeNode page = new DefaultMutableTreeNode(p);
                        if (p.lastReadDate != null)
                        {
                            //TODO: Create some kind of colour-coding scheme for read pages
                        }
                        chapter.add(page);
                    }
                    volume.add(chapter);
                }
                manga.add(volume);
                newModel.insertNodeInto(manga, rootNode, rootNode.getChildCount());
            }
        }
        jtLibraryTree.setModel(newModel);
    }

    private void refreshLibraryView()
    {
//	jtLibraryTree.getModel().
    }

    @Action
    private boolean libraryExists()
    {
        File f = new File(LIBNAME);
        return f.exists();
    }

    private boolean errorLogExists()
    {
        File f = new File(ErrorLogger.LOGFILE);
        return f.exists();
    }

    @Action
    public void importNewManga()
    {
        JFileChooser browser = new JFileChooser(((appSettings.defaultPath != null) ? appSettings.defaultPath : FileSystemView.getFileSystemView().getHomeDirectory().getAbsolutePath()));
        browser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
        if (browser.showOpenDialog(this.getFrame()) == JFileChooser.APPROVE_OPTION)
        {
            File f = browser.getSelectedFile();
            if (f.isDirectory())
            {

//		//Discover subdirectories
//		File[] subDirs = f.listFiles(directoryOnlyFilter);
//		//Group together the subdirectories with the root directory so everything is included in a new array
//		File[] allFolders = new File[subDirs.length + 1];
//		allFolders[0] = f;
//		for (int a = 0; a < subDirs.length; a += 1)
//		{
//		    allFolders[a + 1] = subDirs[a];
//		}
                Manga m = checkMangaDetails(f.getName(), f.getAbsolutePath());
                if (m != null)
                {
                    m.volumes = buildVolumesFromRootDirectory(f);
                    library.add(m);
                    displayMangaLibrary();
                }
            }
        }
    }

    @Action
    public void appendVolume()
    {
        DefaultMutableTreeNode selectedNode = getNodeForTreePath(jtLibraryTree.getSelectionPath());
        if (selectedNode != null)
        {
            Manga m = null;
            if (selectedNode.getUserObject() instanceof Chapter)
            {
                m = (Manga)((DefaultMutableTreeNode)selectedNode.getParent().getParent()).getUserObject();
            }
            else if (selectedNode.getUserObject() instanceof Volume)
            {
                m = (Manga)((DefaultMutableTreeNode)selectedNode.getParent()).getUserObject();
            }
            else if (selectedNode.getUserObject() instanceof Page)
            {
                m = (Manga)((DefaultMutableTreeNode)selectedNode.getParent().getParent().getParent()).getUserObject();
            }
            else if (selectedNode.getUserObject() instanceof Manga)
            {
                m = (Manga)selectedNode.getUserObject();
            }
            else
            {
                JOptionPane.showMessageDialog(this.getFrame(), "Please select a Manga or descendant before appending", "No Manga Selected", JOptionPane.INFORMATION_MESSAGE);
            }
            if (m != null)
            {
                Volume v = new Volume();
            }
        }
    }

    @Action
    public void appendChapter()
    {
        DefaultMutableTreeNode selectedNode = getNodeForTreePath(jtLibraryTree.getSelectionPath());
        if (selectedNode != null)
        {
            Volume v = null;
            if (selectedNode.getUserObject() instanceof Chapter)
            {
                v = (Volume)((DefaultMutableTreeNode)selectedNode.getParent()).getUserObject();
            }
            else if (selectedNode.getUserObject() instanceof Page)
            {
                v = (Volume)((DefaultMutableTreeNode)selectedNode.getParent().getParent()).getUserObject();
            }
            else if (selectedNode.getUserObject() instanceof Volume)
            {
                v = (Volume)selectedNode.getUserObject();
            }
            else
            {
                JOptionPane.showMessageDialog(this.getFrame(), "Please select a Volume or descendant before appending", "No Manga Selected", JOptionPane.INFORMATION_MESSAGE);
            }
            if (v != null)
            {
                Chapter c = new Chapter();
                JFileChooser chapterFolderChooser = new JFileChooser();
                chapterFolderChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
                chapterFolderChooser.setDialogTitle("Choose Folder Containing Image Files");
                if (chapterFolderChooser.showOpenDialog(this.getFrame()) == JFileChooser.APPROVE_OPTION)
                {
                    c.pages = buildPagesFromChapterDirectory(chapterFolderChooser.getSelectedFile());
                    EditChapter ec = new EditChapter();
                    ec.setContent(c);
                    ec.setVisible(true);
                    v.chapters.add(c);
                }
            }
        }
    }

    /**
     * This method matches several conditions which will determine whether the given directory (represented by a File object)
     * contains a Volume, a Chapter or Pages. The rules used are:
     * -A Page directory contains no subdirectories, just files
     * -A Chapter directory contains subdirectories containing Pages
     * -A Volume directory contains subdirectories containing sub-subdirectories.
     * Neither Chapters or Volumes contain raw Page files. If images are found, they are appended as a new Chapter to the end of the Volume
     * @param root
     * @return 
     */
    private ArrayList<Volume> buildVolumesFromRootDirectory(File root)
    {
        if (root.isDirectory())
        {
            ArrayList<Volume> volumeList = new ArrayList<Volume>();
            int volumeCount = 1;
            File[] volumeDirs = root.listFiles(directoryOnlyFilter);
            if (volumeDirs.length > 0) //We have subfolders here, so this is either a multi-volume or a multi-chapter Manga
            {
                if (volumeDirs[0].listFiles(directoryOnlyFilter).length == 0) //If this folder has no subfolder then it must actually be a chapter
                {
                    Volume v = new Volume(); //So build the manga accordingly; one Volume, multiple Chapters
                    v.volumeNo = 1;
                    v.chapters = buildChaptersFromVolumeDirectory(root);
                    volumeList.add(v);
                    return volumeList;
                }
                else //Otherwise, if the testbed has multiple subdirectories
                {
                    for (File volume : volumeDirs) //Create the manga as anticipated, multiple Volumes, multiple Chapters
                    {
                        Pattern findFirstInt = Pattern.compile("\\d+");
                        Matcher makeMatch = findFirstInt.matcher(volume.getName());
                        Volume v = new Volume();
                        v.volumeNo = (makeMatch.find()) ? Integer.parseInt(makeMatch.group()) : volumeCount;
                        v.chapters = buildChaptersFromVolumeDirectory(volume);
                        volumeList.add(v);
                        volumeCount += 1;
                    }
                    return volumeList;
                }
            }
            else //However, if there are no subfolders, then this is a single-Volume, single-Chapter manga
            {
                Volume v = new Volume();
                v.volumeNo = 1;
                v.chapters = new ArrayList<Chapter>();
                v.chapters.add(new Chapter(1));
                v.chapters.get(0).pages = buildPagesFromChapterDirectory(root);
                volumeList.add(v);
                return volumeList;
            }
        }
        return null;
    }

    private ArrayList<Chapter> buildChaptersFromVolumeDirectory(File volume)
    {
        if (volume.isDirectory())
        {
            ArrayList<Chapter> chapterList = new ArrayList<Chapter>();
            int chapterCount = 1;
            File[] chapterDirs = volume.listFiles(directoryOnlyFilter);
            for (File dir : chapterDirs)
            {
                Pattern findFirstInt = Pattern.compile("\\d+");
                Matcher makeMatch = findFirstInt.matcher(dir.getName());
                int chapterNo = (makeMatch.find()) ? Integer.parseInt(makeMatch.group()) : chapterCount;
                Chapter c = new Chapter(chapterNo);
//		int highestChapterCount = getHighestCountOfExistingChapter(m, chapterID);
//		if (highestChapterCount > 0)
//		{
//		    c = new Chapter(chapterID, (highestChapterCount + 1));
//		}
//		else
//		{
//		    c = new Chapter(chapterID);
//		}
                c.pages = buildPagesFromChapterDirectory(dir);
                chapterList.add(c);
                chapterCount += 1;
            }
            return chapterList;
        }
        else
        {
            return null;
        }
    }

    private ArrayList<Page> buildPagesFromChapterDirectory(File chapter)
    {
        if (chapter.isDirectory())
        {
            ArrayList<Page> pageList = new ArrayList<Page>();
            int pageCount = 1;
            File[] imageFiles = chapter.listFiles(azureImageFileFilter);
            for (File image : imageFiles)
            {
                Pattern findFirstInt = Pattern.compile("\\d+");
                Matcher makeMatch = findFirstInt.matcher(image.getName());
                int pageNo = (makeMatch.find()) ? Integer.parseInt(makeMatch.group()) : pageCount;
                Page p = new Page(pageNo, image.getAbsolutePath());
                pageList.add(p);
                pageCount += 1;
            }
            return pageList;
        }
        else
        {
            return null;
        }
    }

    /**
     * Pop up a dialog box with Azure's suggested name for the Manga. This allows the user to edit the name before it is committed
     * @param mangaName
     * @param mangaPages
     * @param folderPath
     * @return 
     */
    private Manga checkMangaDetails(String mangaName, String folderPath)
    {

        Manga newManga = new Manga(0, 0, mangaName, "<<This is Azure's best guess at the Manga you have selected. Change these values to suit>>", 0, (Calendar.getInstance().get(Calendar.YEAR)), 0);
        EditManga em = new EditManga(); //I'm quite surprised this works, actually. I'm using the EditManga form, but extracting the panel containing the actual input fields. This means I can use the same form for both new and existing, and use the same logic
        em.setContent(newManga);
        if (JOptionPane.showConfirmDialog(this.getComponent(), em.getInfoPanel(), "New Manga: " + folderPath, JOptionPane.OK_CANCEL_OPTION) == JOptionPane.OK_OPTION)
        {
            em.saveChanges();
            return newManga;
        }
        else
        {
            if (JOptionPane.showConfirmDialog(this.getComponent(), "Abort new Manga creation?", "Cancel New Manga Creation", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE) == JOptionPane.YES_OPTION)
            {
                return null;
            }
            else //TODO: Remove recursion here. Just asking for trouble
            {
                return checkMangaDetails(mangaName, folderPath);
            }
        }
    }

//    private String createMangaName(String input)
//    {
//
//	String output = "";
//	input = input.replace('_', ' ');
//	if (Pattern.matches("[[+]", input))
//	{
//	}
//	return output;
//    }
    private boolean mangaExists(String mangaName)
    {
        return false;
    }

    private int getHighestCountOfExistingChapter(Volume newVolume, int chapterNo)
    {
        int highestCount = 0;
        for (Chapter c : newVolume.chapters)
        {
            if (c.chapterNo == chapterNo)
            {
                highestCount += 1;
            }
        }
        return highestCount;
    }

    @Action
    private void selectPreviousChapter()
    {
        if (activeNode != null)
        {
            DefaultMutableTreeNode chapterNode = (DefaultMutableTreeNode)activeNode.getParent();
            int previousChapterNo = ((Chapter)chapterNode.getUserObject()).chapterNo - 1;
            do
            {
                chapterNode = chapterNode.getPreviousSibling();
            }
            while ((chapterNode != null) && (((Chapter)chapterNode.getUserObject()).chapterNo != previousChapterNo) && ((Chapter)chapterNode.getUserObject()).chapterVersion != 1);

            if (chapterNode != null)
            {
                navigateToNode(chapterNode.getFirstLeaf());
                jbPrevious.setText("Previous");
            }
        }
    }

    @Action
    private void selectNextChapter()
    {
        if (activeNode != null)
        {
            DefaultMutableTreeNode chapterNode = (DefaultMutableTreeNode)activeNode.getParent();

            int nextChapterNo = ((Chapter)chapterNode.getUserObject()).chapterNo + 1;
            do
            {
                chapterNode = chapterNode.getNextSibling();
            }
            while ((chapterNode != null) && (((Chapter)chapterNode.getUserObject()).chapterNo != nextChapterNo));

            if (chapterNode != null)
            {
                navigateToNode(chapterNode.getFirstLeaf());
                jbNext.setText("Next");
            }
        }
    }

//    private boolean chapterExistsForMangaInLibrary(String mangaName, int chapterNo)
//    {
//	return false;
//    }
//
//    private boolean pageExistsForMangaAndChapter(String mangaName, int chapterNo, int pageNo)
//    {
//	return false;
//    }
    private void navigateToNodeByPageNo(int pageNo)
    {
        if (activeNode != null)
        {
            TreeNode parentNode = activeNode.getParent();
            for (int x = 0; x < parentNode.getChildCount(); x += 1)
            {
                DefaultMutableTreeNode node = (DefaultMutableTreeNode)parentNode.getChildAt(x);
                if (node.getUserObject() instanceof Page)
                {
                    Page nodePage = (Page)node.getUserObject();
                    if (nodePage.pageNo == pageNo)
                    {
                        navigateToNode(node);
                        break;
                    }
                }
            }
        }
    }

    @Action
    public void navigateToFirstPage()
    {
        if (activeNode != null)
        {
            TreeNode parentNode = activeNode.getParent();
            DefaultMutableTreeNode firstNode = (DefaultMutableTreeNode)parentNode.getChildAt(0);
            navigateToNode(firstNode);
        }
    }

    @Action
    public void navigateToLastPage()
    {
        if (activeNode != null)
        {
            TreeNode parentNode = activeNode.getParent();
            DefaultMutableTreeNode lastNode = (DefaultMutableTreeNode)parentNode.getChildAt((parentNode.getChildCount() - 1));
            navigateToNode(lastNode);
        }
    }

    @Action
    public void navigateToPreviousPage()
    {
        if (activeNode != null)
        {
            DefaultMutableTreeNode previousNode = activeNode.getPreviousNode();
            if (previousNode.equals(activeNode.getParent()))
            {
                selectPreviousChapter();
            }
            else
            {
                navigateToNode(previousNode);
            }
        }
    }

    @Action
    public void navigateToNextPage()
    {
        if (activeNode != null)
        {
            DefaultMutableTreeNode nextNode = activeNode.getNextSibling();
            if (nextNode != null)
            {
                navigateToNode(nextNode);
            }
            else
            {
                selectNextChapter();
            }
        }
    }

    private void navigateToNode(DefaultMutableTreeNode node)
    {
        if (node != null)
        {
            if ((node.getUserObject() instanceof Page))
            {
                activeNode = node;
                DefaultMutableTreeNode chapter = (DefaultMutableTreeNode)node.getParent();
                appSettings.lastOpenChapterID = ((Chapter)chapter.getUserObject()).chapterID;
                DefaultMutableTreeNode volume = (DefaultMutableTreeNode)chapter.getParent();
                appSettings.lastOpenVolumeID = ((Volume)volume.getUserObject()).volumeID;
                DefaultMutableTreeNode manga = (DefaultMutableTreeNode)volume.getParent();
                appSettings.lastOpenMangaID = ((Manga)manga.getUserObject()).libraryID;
                jlTitle.setText(manga.toString() + " - " + chapter.toString() + " - " + node.toString());
                Page toDisplay = (Page)node.getUserObject();
                appSettings.lastOpenPageID = toDisplay.pageID;
                jftfPageNumber.setValue(toDisplay.pageNo);
                display(toDisplay.filename);
                if (nodeHasNext(node))
                {
                    jbNext.setText("Next");
                }
                else
                {
                    jbNext.setText("Next Chapter");
                }
                if (nodeHasPrevious(node))
                {
                    jbPrevious.setText("Previous");
                }
                else
                {
                    jbPrevious.setText("Previous Chapter");
                }
                jtLibraryTree.setSelectionPath(new TreePath(node.getPath()));
                toDisplay.lastReadDate = Calendar.getInstance().getTime();
                toDisplay.isModified = true;
            }
        }
    }

    /**
     * Test whether a given tree node has a succeeding sibling
     * @param node
     * @return True if the node has a next, false otherwise
     */
    private boolean nodeHasNext(DefaultMutableTreeNode node)
    {
        if (node.getParent().getIndex(node) == (node.getParent().getChildCount() - 1))
        {
            return false;
        }
        else
        {
            return (node.getParent().getChildAt(node.getParent().getIndex(node) + 1) != null);
        }
    }

    /**
     * Test whether a given tree node has a preceeding sibling
     * @param node
     * @return True if the node has a previous, false otherwise
     */
    private boolean nodeHasPrevious(DefaultMutableTreeNode node)
    {
        if (node.getParent().getIndex(node) == 0)
        {
            return false;
        }
        else
        {
            return (node.getParent().getChildAt(node.getParent().getIndex(node) - 1) != null);
        }
    }

    public Image getActiveImage()
    {
        return activeImage;
    }

    public void returnFromSettings()
    {
        displayMangaLibrary();
    }

    public ArrayList<Manga> getMangaLibrary()
    {
        return this.library;
    }

    public void editChapter(DefaultMutableTreeNode node)
    {
        if (node.getUserObject() instanceof Chapter)
        {
            Chapter c = (Chapter)node.getUserObject();
            EditChapter popup = new EditChapter();
            popup.setTitle(node.getParent().toString() + " - " + c.toString());
            popup.setContent(c);
            popup.setVisible(true);
        }
    }

    public void editManga(DefaultMutableTreeNode node)
    {
        if (node.getUserObject() instanceof Manga)
        {
            Manga m = (Manga)node.getUserObject();
            EditManga popup = new EditManga();
            popup.setTitle("Manga: " + m.toString());
            popup.setContent(m);
            popup.setVisible(true);
        }
    }

    private void handleMouseClick(MouseEvent me)
    {
        if ((me.getButton() == MouseEvent.BUTTON1) && (me.getClickCount() > 1))
        {
            TreePath path = jtLibraryTree.getPathForLocation(me.getX(), me.getY());
            handleNodeAction(path);
        }
    }

    private void handleNodeAction(TreePath path)
    {
        if (path != null)
        {
            DefaultMutableTreeNode selectedNode = getNodeForTreePath(path);
            if (selectedNode.getUserObject() instanceof Page)
            {
                navigateToNode(selectedNode);
            }
//	    else if (selectedNode.getUserObject() instanceof Chapter)
//	    {
//		editChapter(selectedNode);
//	    }
        }
    }

    private DefaultMutableTreeNode getNodeForTreePath(TreePath path)
    {
        return (DefaultMutableTreeNode)path.getLastPathComponent();
    }

    public void display(String pageFileName)
    {
        try
        {
            jtpViewSwitch.setSelectedIndex(0);
            BufferedImage mangaPage = ImageIO.read(new File(pageFileName));
            activeImage = mangaPage.getScaledInstance(-1, -1, Image.SCALE_SMOOTH);
            activeImageSize = new Dimension(mangaPage.getWidth(), mangaPage.getHeight());
            scaleAndDisplayImage();

        }
        catch (FileNotFoundException fnfe)
        {
            ErrorLogger.logErrorMessage(this.getClass().getName(), 916, "Could not load requested image file: " + fnfe.getLocalizedMessage());
        }
        catch (IOException io)
        {
            ErrorLogger.logErrorMessage(this.getClass().getName(), 920, "IO Exception: " + io.getLocalizedMessage());
        }
    }

    public void scaleAndDisplayImage()
    {
        if (activeImage != null)
        {
//	    jlImage.setIcon(new ImageIcon(activeImage.getScaledInstance(-1, jlImage.getHeight(), java.awt.Image.SCALE_SMOOTH)));

            int displayHeight = jlImage.getHeight(), displayWidth = jlImage.getWidth();
            if (displayHeight >= displayWidth) //Viewer is displaying portrait or square
            {
                if (activeImageSize.height >= activeImageSize.width) //Page is also portrait or square
                {
                    jlImage.setIcon(new ImageIcon(activeImage.getScaledInstance(-1, displayHeight, java.awt.Image.SCALE_SMOOTH)));
                }
                else //Page is landscape
                {
                    jlImage.setIcon(new ImageIcon(activeImage.getScaledInstance(displayWidth, -1, java.awt.Image.SCALE_SMOOTH)));
                }
            }
            else //Viewer is landscape
            {
                jlImage.setIcon(new ImageIcon(activeImage.getScaledInstance(-1, displayHeight, java.awt.Image.SCALE_SMOOTH)));
            }
        }
    }

    public TreeModel getTreeModel()
    {
        return newModel;
    }

    @Action
    public void showAboutBox()
    {
        if (aboutBox == null)
        {
            JFrame mainFrame = Main.getApplication().getMainFrame();
            aboutBox = new MainAboutBox(mainFrame);
            aboutBox.setLocationRelativeTo(mainFrame);
        }
        Main.getApplication().show(aboutBox);
    }

    public AppSettings getAppSettings()
    {
        return this.appSettings;
    }

    /** This method is called from within the constructor to
     * initialise the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        mainPanel = new javax.swing.JPanel();
        jtpViewSwitch = new javax.swing.JTabbedPane();
        jpViewer = new javax.swing.JPanel();
        jPanel1 = new javax.swing.JPanel();
        jlTitle = new javax.swing.JLabel();
        jpImage = new javax.swing.JPanel();
        jlImage = new javax.swing.JLabel();
        jpButtonBar = new javax.swing.JPanel();
        jbFirst = new javax.swing.JButton();
        jbPrevious = new javax.swing.JButton();
        jbFullScreen = new javax.swing.JButton();
        jbNext = new javax.swing.JButton();
        jbLast = new javax.swing.JButton();
        jlPage = new javax.swing.JLabel();
        jftfPageNumber = new javax.swing.JFormattedTextField();
        jlIsNotANumber = new javax.swing.JLabel();
        jlIsNotANumber.setVisible(false);
        jpLibrary = new javax.swing.JPanel();
        jspLibrary = new javax.swing.JScrollPane();
        jtLibraryTree = new javax.swing.JTree();
        jpLibraryButtonBar = new javax.swing.JPanel();
        jbImport = new javax.swing.JButton();
        jbResume = new javax.swing.JButton();
        jbAppendVolume = new javax.swing.JButton();
        jbAppendChapter = new javax.swing.JButton();
        menuBar = new javax.swing.JMenuBar();
        javax.swing.JMenu fileMenu = new javax.swing.JMenu();
        saveMenuItem = new javax.swing.JMenuItem();
        jSeparator4 = new javax.swing.JPopupMenu.Separator();
        importMenuItem = new javax.swing.JMenuItem();
        exportMenuItem = new javax.swing.JMenuItem();
        jSeparator5 = new javax.swing.JPopupMenu.Separator();
        javax.swing.JMenuItem exitMenuItem = new javax.swing.JMenuItem();
        navigateMenu = new javax.swing.JMenu();
        jMenuItem3 = new javax.swing.JMenuItem();
        jMenuItem4 = new javax.swing.JMenuItem();
        jMenuItem5 = new javax.swing.JMenuItem();
        jMenuItem6 = new javax.swing.JMenuItem();
        jSeparator1 = new javax.swing.JPopupMenu.Separator();
        jMenuItem7 = new javax.swing.JMenuItem();
        jMenuItem8 = new javax.swing.JMenuItem();
        jSeparator2 = new javax.swing.JPopupMenu.Separator();
        jMenuItem9 = new javax.swing.JMenuItem();
        jSeparator3 = new javax.swing.JPopupMenu.Separator();
        jMenuItem10 = new javax.swing.JMenuItem();
        toolsMenu = new javax.swing.JMenu();
        jMenuItem1 = new javax.swing.JMenuItem();
        javax.swing.JMenu helpMenu = new javax.swing.JMenu();
        javax.swing.JMenuItem aboutMenuItem = new javax.swing.JMenuItem();
        jpmTreeContextMenu = new javax.swing.JPopupMenu();
        jcmeEditItem = new javax.swing.JMenuItem();
        jcmeDeleteItem = new javax.swing.JMenuItem();
        jcmAppend = new javax.swing.JMenu();
        jcmeAppendVolume = new javax.swing.JMenuItem();
        jcmeAppendChapter = new javax.swing.JMenuItem();

        mainPanel.setMinimumSize(MINIMUM_SIZE);
        mainPanel.setName("mainPanel"); // NOI18N
        mainPanel.setPreferredSize(new java.awt.Dimension(600, 800));
        mainPanel.addHierarchyBoundsListener(new java.awt.event.HierarchyBoundsListener() {
            public void ancestorMoved(java.awt.event.HierarchyEvent evt) {
            }
            public void ancestorResized(java.awt.event.HierarchyEvent evt) {
                ResizeWindow(evt);
            }
        });
        mainPanel.addAncestorListener(new javax.swing.event.AncestorListener() {
            public void ancestorMoved(javax.swing.event.AncestorEvent evt) {
                WindowMoved(evt);
            }
            public void ancestorAdded(javax.swing.event.AncestorEvent evt) {
            }
            public void ancestorRemoved(javax.swing.event.AncestorEvent evt) {
            }
        });
        mainPanel.setLayout(new java.awt.GridLayout(1, 0));

        jtpViewSwitch.setMinimumSize(MINIMUM_SIZE);
        jtpViewSwitch.setName("jtpViewerLibrary"); // NOI18N
        jtpViewSwitch.addChangeListener(new javax.swing.event.ChangeListener() {
            public void stateChanged(javax.swing.event.ChangeEvent evt) {
                tabChangeHandler(evt);
            }
        });

        jpViewer.setName("jpViewer"); // NOI18N
        jpViewer.addKeyListener(new java.awt.event.KeyAdapter() {
            public void keyPressed(java.awt.event.KeyEvent evt) {
                jpViewerKeyPressed(evt);
            }
        });
        jpViewer.setLayout(new javax.swing.BoxLayout(jpViewer, javax.swing.BoxLayout.Y_AXIS));

        jPanel1.setMaximumSize(new java.awt.Dimension(32767, 20));
        jPanel1.setName("jPanel1"); // NOI18N
        jPanel1.setPreferredSize(new java.awt.Dimension(670, 30));

        jlTitle.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
        org.jdesktop.application.ResourceMap resourceMap = org.jdesktop.application.Application.getInstance(azure.Main.class).getContext().getResourceMap(MainView.class);
        jlTitle.setText(resourceMap.getString("jlTitle.text")); // NOI18N
        jlTitle.setName("jlTitle"); // NOI18N

        org.jdesktop.layout.GroupLayout jPanel1Layout = new org.jdesktop.layout.GroupLayout(jPanel1);
        jPanel1.setLayout(jPanel1Layout);
        jPanel1Layout.setHorizontalGroup(
            jPanel1Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(jlTitle, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 773, Short.MAX_VALUE)
        );
        jPanel1Layout.setVerticalGroup(
            jPanel1Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(jlTitle, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 30, Short.MAX_VALUE)
        );

        jpViewer.add(jPanel1);

        jpImage.setBorder(javax.swing.BorderFactory.createEtchedBorder());
        jpImage.setName("jpImage"); // NOI18N
        jpImage.setPreferredSize(new java.awt.Dimension(0, 0));
        jpImage.setLayout(new java.awt.GridLayout(1, 1));

        jlImage.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
        jlImage.setText(resourceMap.getString("jlImage.text")); // NOI18N
        jlImage.setToolTipText(resourceMap.getString("jlImage.toolTipText")); // NOI18N
        jlImage.setDoubleBuffered(true);
        jlImage.setMaximumSize(jpImage.getSize());
        jlImage.setName("jlImage"); // NOI18N
        jlImage.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mouseClicked(java.awt.event.MouseEvent evt) {
                jlImageMouseClicked(evt);
            }
        });
        jpImage.add(jlImage);

        jpViewer.add(jpImage);

        jpButtonBar.setMaximumSize(new java.awt.Dimension(32767, 39));
        jpButtonBar.setName("jpButtonBar"); // NOI18N
        jpButtonBar.addKeyListener(new java.awt.event.KeyAdapter() {
            public void keyPressed(java.awt.event.KeyEvent evt) {
                jpButtonBarKeyPressed(evt);
            }
        });

        javax.swing.ActionMap actionMap = org.jdesktop.application.Application.getInstance(azure.Main.class).getContext().getActionMap(MainView.class, this);
        jbFirst.setAction(actionMap.get("navigateToFirstPage")); // NOI18N
        jbFirst.setText(resourceMap.getString("jbFirst.text")); // NOI18N
        jbFirst.setName("jbFirst"); // NOI18N
        jpButtonBar.add(jbFirst);

        jbPrevious.setAction(actionMap.get("navigateToPreviousPage")); // NOI18N
        jbPrevious.setText(resourceMap.getString("jbPrevious.text")); // NOI18N
        jbPrevious.setName("jbPrevious"); // NOI18N
        jpButtonBar.add(jbPrevious);

        jbFullScreen.setAction(actionMap.get("makeFullScreen")); // NOI18N
        jbFullScreen.setText(resourceMap.getString("jbFullScreen.text")); // NOI18N
        jbFullScreen.setName("jbFullScreen"); // NOI18N
        jpButtonBar.add(jbFullScreen);

        jbNext.setAction(actionMap.get("navigateToNextPage")); // NOI18N
        jbNext.setText(resourceMap.getString("jbNext.text")); // NOI18N
        jbNext.setName("jbNext"); // NOI18N
        jpButtonBar.add(jbNext);

        jbLast.setAction(actionMap.get("navigateToLastPage")); // NOI18N
        jbLast.setText(resourceMap.getString("jbLast.text")); // NOI18N
        jbLast.setName("jbLast"); // NOI18N
        jpButtonBar.add(jbLast);

        jlPage.setText(resourceMap.getString("jlPage.text")); // NOI18N
        jlPage.setName("jlPage"); // NOI18N
        jpButtonBar.add(jlPage);

        jftfPageNumber.setFormatterFactory(new javax.swing.text.DefaultFormatterFactory(new javax.swing.text.NumberFormatter(new java.text.DecimalFormat("#0"))));
        jftfPageNumber.setMinimumSize(new java.awt.Dimension(50, 20));
        jftfPageNumber.setName("jftfPageNumber"); // NOI18N
        jftfPageNumber.setPreferredSize(new java.awt.Dimension(50, 20));
        jftfPageNumber.addKeyListener(new java.awt.event.KeyAdapter() {
            public void keyPressed(java.awt.event.KeyEvent evt) {
                jftfPageNumberKeyPressed(evt);
            }
        });
        jpButtonBar.add(jftfPageNumber);

        jlIsNotANumber.setText(resourceMap.getString("jlIsNotANumber.text")); // NOI18N
        jlIsNotANumber.setName("jlIsNotANumber"); // NOI18N
        jpButtonBar.add(jlIsNotANumber);

        jpViewer.add(jpButtonBar);

        jtpViewSwitch.addTab(resourceMap.getString("jpViewer.TabConstraints.tabTitle"), jpViewer); // NOI18N

        jpLibrary.setName("jpLibrary"); // NOI18N

        jspLibrary.setName("jspLibrary"); // NOI18N

        javax.swing.tree.DefaultMutableTreeNode treeNode1 = new javax.swing.tree.DefaultMutableTreeNode("Library");
        jtLibraryTree.setModel(new javax.swing.tree.DefaultTreeModel(treeNode1));
        jtLibraryTree.setCellRenderer(renderer);
        jtLibraryTree.setComponentPopupMenu(jpmTreeContextMenu);
        jtLibraryTree.setName("jtLibrary"); // NOI18N
        jtLibraryTree.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mouseClicked(java.awt.event.MouseEvent evt) {
                jtLibraryTreeMouseClicked(evt);
            }
        });
        jspLibrary.setViewportView(jtLibraryTree);

        jpLibraryButtonBar.setName("jpLibraryButtonBar"); // NOI18N

        jbImport.setAction(actionMap.get("importNewManga")); // NOI18N
        jbImport.setText(resourceMap.getString("jbImport.text")); // NOI18N
        jbImport.setName("jbImport"); // NOI18N

        jbResume.setAction(actionMap.get("resume")); // NOI18N
        jbResume.setText("Resume"); // NOI18N
        jbResume.setName("jbResume"); // NOI18N

        jbAppendVolume.setAction(actionMap.get("appendVolume")); // NOI18N
        jbAppendVolume.setText("Append Volume"); // NOI18N
        jbAppendVolume.setName("jbAppendVolume"); // NOI18N

        jbAppendChapter.setAction(actionMap.get("appendChapter")); // NOI18N
        jbAppendChapter.setText("Append Chapter"); // NOI18N
        jbAppendChapter.setName("jbAppendChapter"); // NOI18N

        org.jdesktop.layout.GroupLayout jpLibraryButtonBarLayout = new org.jdesktop.layout.GroupLayout(jpLibraryButtonBar);
        jpLibraryButtonBar.setLayout(jpLibraryButtonBarLayout);
        jpLibraryButtonBarLayout.setHorizontalGroup(
            jpLibraryButtonBarLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(jpLibraryButtonBarLayout.createSequentialGroup()
                .add(jbImport)
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.UNRELATED)
                .add(jbResume)
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.UNRELATED)
                .add(jbAppendVolume)
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.UNRELATED)
                .add(jbAppendChapter)
                .addContainerGap(306, Short.MAX_VALUE))
        );
        jpLibraryButtonBarLayout.setVerticalGroup(
            jpLibraryButtonBarLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(org.jdesktop.layout.GroupLayout.TRAILING, jpLibraryButtonBarLayout.createSequentialGroup()
                .addContainerGap(17, Short.MAX_VALUE)
                .add(jpLibraryButtonBarLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
                    .add(jbImport)
                    .add(jbResume)
                    .add(jbAppendVolume)
                    .add(jbAppendChapter))
                .addContainerGap())
        );

        org.jdesktop.layout.GroupLayout jpLibraryLayout = new org.jdesktop.layout.GroupLayout(jpLibrary);
        jpLibrary.setLayout(jpLibraryLayout);
        jpLibraryLayout.setHorizontalGroup(
            jpLibraryLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(jpLibraryButtonBar, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
            .add(jspLibrary, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 773, Short.MAX_VALUE)
        );
        jpLibraryLayout.setVerticalGroup(
            jpLibraryLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(org.jdesktop.layout.GroupLayout.TRAILING, jpLibraryLayout.createSequentialGroup()
                .add(jspLibrary, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 647, Short.MAX_VALUE)
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                .add(jpLibraryButtonBar, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))
        );

        jtpViewSwitch.addTab(resourceMap.getString("jpLibrary.TabConstraints.tabTitle"), jpLibrary); // NOI18N

        mainPanel.add(jtpViewSwitch);

        menuBar.setName("menuBar"); // NOI18N

        fileMenu.setText(resourceMap.getString("fileMenu.text")); // NOI18N
        fileMenu.setName("fileMenu"); // NOI18N

        saveMenuItem.setAction(actionMap.get("save")); // NOI18N
        saveMenuItem.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_S, java.awt.event.InputEvent.META_MASK));
        saveMenuItem.setText(resourceMap.getString("saveMenuItem.text")); // NOI18N
        saveMenuItem.setName("saveMenuItem"); // NOI18N
        fileMenu.add(saveMenuItem);

        jSeparator4.setName("jSeparator4"); // NOI18N
        fileMenu.add(jSeparator4);

        importMenuItem.setAction(actionMap.get("importLibrary")); // NOI18N
        importMenuItem.setText(resourceMap.getString("importMenuItem.text")); // NOI18N
        importMenuItem.setEnabled(false);
        importMenuItem.setName("importMenuItem"); // NOI18N
        fileMenu.add(importMenuItem);

        exportMenuItem.setAction(actionMap.get("exportLibrary")); // NOI18N
        exportMenuItem.setText(resourceMap.getString("exportMenuItem.text")); // NOI18N
        exportMenuItem.setName("exportMenuItem"); // NOI18N
        fileMenu.add(exportMenuItem);

        jSeparator5.setName("jSeparator5"); // NOI18N
        fileMenu.add(jSeparator5);

        exitMenuItem.setAction(actionMap.get("quit")); // NOI18N
        exitMenuItem.setName("exitMenuItem"); // NOI18N
        exitMenuItem.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                exitMenuItemActionPerformed(evt);
            }
        });
        fileMenu.add(exitMenuItem);

        menuBar.add(fileMenu);

        navigateMenu.setText(resourceMap.getString("navigateMenu.text")); // NOI18N
        navigateMenu.setName("navigateMenu"); // NOI18N

        jMenuItem3.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_HOME, 0));
        jMenuItem3.setText(resourceMap.getString("jMenuItem3.text")); // NOI18N
        jMenuItem3.setName("jMenuItem3"); // NOI18N
        navigateMenu.add(jMenuItem3);

        jMenuItem4.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_LEFT, 0));
        jMenuItem4.setText(resourceMap.getString("jMenuItem4.text")); // NOI18N
        jMenuItem4.setName("jMenuItem4"); // NOI18N
        navigateMenu.add(jMenuItem4);

        jMenuItem5.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_RIGHT, 0));
        jMenuItem5.setText(resourceMap.getString("jMenuItem5.text")); // NOI18N
        jMenuItem5.setName("jMenuItem5"); // NOI18N
        navigateMenu.add(jMenuItem5);

        jMenuItem6.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_END, 0));
        jMenuItem6.setText(resourceMap.getString("jMenuItem6.text")); // NOI18N
        jMenuItem6.setName("jMenuItem6"); // NOI18N
        navigateMenu.add(jMenuItem6);

        jSeparator1.setName("jSeparator1"); // NOI18N
        navigateMenu.add(jSeparator1);

        jMenuItem7.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_LEFT, java.awt.event.InputEvent.SHIFT_MASK));
        jMenuItem7.setText(resourceMap.getString("jMenuItem7.text")); // NOI18N
        jMenuItem7.setName("jMenuItem7"); // NOI18N
        navigateMenu.add(jMenuItem7);

        jMenuItem8.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_RIGHT, java.awt.event.InputEvent.SHIFT_MASK));
        jMenuItem8.setText(resourceMap.getString("jMenuItem8.text")); // NOI18N
        jMenuItem8.setName("jMenuItem8"); // NOI18N
        navigateMenu.add(jMenuItem8);

        jSeparator2.setName("jSeparator2"); // NOI18N
        navigateMenu.add(jSeparator2);

        jMenuItem9.setAction(actionMap.get("makeFullScreen")); // NOI18N
        jMenuItem9.setText(resourceMap.getString("jMenuItem9.text")); // NOI18N
        jMenuItem9.setName("jMenuItem9"); // NOI18N
        navigateMenu.add(jMenuItem9);

        jSeparator3.setName("jSeparator3"); // NOI18N
        navigateMenu.add(jSeparator3);

        jMenuItem10.setAction(actionMap.get("resume")); // NOI18N
        jMenuItem10.setText(resourceMap.getString("jMenuItem10.text")); // NOI18N
        jMenuItem10.setName("jMenuItem10"); // NOI18N
        navigateMenu.add(jMenuItem10);

        menuBar.add(navigateMenu);

        toolsMenu.setText(resourceMap.getString("toolsMenu.text")); // NOI18N
        toolsMenu.setName("toolsMenu"); // NOI18N

        jMenuItem1.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_COMMA, java.awt.event.InputEvent.META_MASK));
        jMenuItem1.setText(resourceMap.getString("jMenuItem1.text")); // NOI18N
        jMenuItem1.setName("jMenuItem1"); // NOI18N
        jMenuItem1.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMenuItem1ActionPerformed(evt);
            }
        });
        toolsMenu.add(jMenuItem1);

        menuBar.add(toolsMenu);

        helpMenu.setText(resourceMap.getString("helpMenu.text")); // NOI18N
        helpMenu.setName("helpMenu"); // NOI18N

        aboutMenuItem.setAction(actionMap.get("showAboutBox")); // NOI18N
        aboutMenuItem.setName("aboutMenuItem"); // NOI18N
        helpMenu.add(aboutMenuItem);

        menuBar.add(helpMenu);

        jpmTreeContextMenu.setName("jpmTreeContextMenu"); // NOI18N

        jcmeEditItem.setAction(actionMap.get("handleItemEditClick")); // NOI18N
        jcmeEditItem.setName("jcmeEditItem"); // NOI18N
        jpmTreeContextMenu.add(jcmeEditItem);

        jcmeDeleteItem.setAction(actionMap.get("deleteSelectedItem")); // NOI18N
        jcmeDeleteItem.setName("jcmeDeleteItem"); // NOI18N
        jpmTreeContextMenu.add(jcmeDeleteItem);

        jcmAppend.setText("Append"); // NOI18N
        jcmAppend.setName("jcmAppend"); // NOI18N

        jcmeAppendVolume.setAction(actionMap.get("appendVolume")); // NOI18N
        jcmeAppendVolume.setName("jcmeAppendVolume"); // NOI18N
        jcmAppend.add(jcmeAppendVolume);

        jcmeAppendChapter.setAction(actionMap.get("appendChapter")); // NOI18N
        jcmeAppendChapter.setName("jcmeAppendChapter"); // NOI18N
        jcmAppend.add(jcmeAppendChapter);

        jpmTreeContextMenu.add(jcmAppend);

        setComponent(mainPanel);
        setMenuBar(menuBar);
    }// </editor-fold>//GEN-END:initComponents

private void ResizeWindow(java.awt.event.HierarchyEvent evt) {//GEN-FIRST:event_ResizeWindow
    int currentWidth = super.getFrame().getWidth();
    int currentHeight = super.getFrame().getHeight();
    boolean doResize = false;
    if (currentWidth < MINIMUM_SIZE.width)
    {
        currentWidth = MINIMUM_SIZE.width;
        doResize = true;
    }
    if (currentHeight < MINIMUM_SIZE.height)
    {
        currentHeight = MINIMUM_SIZE.height;
        doResize = true;
    }
    if (doResize)
    {
        super.getFrame().setSize(currentWidth, currentHeight);
    }

    logWindowBounds();
}//GEN-LAST:event_ResizeWindow

private void WindowMoved(javax.swing.event.AncestorEvent evt) {//GEN-FIRST:event_WindowMoved
    logWindowBounds();
}//GEN-LAST:event_WindowMoved

private void exitMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_exitMenuItemActionPerformed
    saveSettings();
}//GEN-LAST:event_exitMenuItemActionPerformed

private void jtLibraryTreeMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_jtLibraryTreeMouseClicked
    handleMouseClick(evt);
}//GEN-LAST:event_jtLibraryTreeMouseClicked

private void jftfPageNumberKeyPressed(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_jftfPageNumberKeyPressed
    if (evt.getKeyCode() == RETURN_KEY)
    {
        try
        {
            navigateToNodeByPageNo(Integer.parseInt(jftfPageNumber.getText()));
            jlIsNotANumber.setVisible(false);
        }
        catch (NumberFormatException nfe)
        {
            jlIsNotANumber.setVisible(true);
        }
    }
}//GEN-LAST:event_jftfPageNumberKeyPressed

private void jMenuItem1ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMenuItem1ActionPerformed
    Settings s = new Settings();
    s.setParentForm(this);
    s.showCurrentSettings();
    s.setVisible(true);
}//GEN-LAST:event_jMenuItem1ActionPerformed

private void tabChangeHandler(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_tabChangeHandler
    switch (jtpViewSwitch.getSelectedIndex())
    {
        case VIEWER_TAB:
        {
            break;
        }
        case LIBRARY_TAB:
        {
            break;
        }
    }
}//GEN-LAST:event_tabChangeHandler

private void jpViewerKeyPressed(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_jpViewerKeyPressed
    JOptionPane.showMessageDialog(jbImport, "Key Code: " + evt.getKeyCode());
}//GEN-LAST:event_jpViewerKeyPressed

private void jlImageMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_jlImageMouseClicked
    if ((evt.getButton() == MouseEvent.BUTTON1) && (evt.getClickCount() == 1))
    {
        scaleAndDisplayImage();
    }
}//GEN-LAST:event_jlImageMouseClicked

private void jpButtonBarKeyPressed(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_jpButtonBarKeyPressed
    JOptionPane.showMessageDialog(this.jpButtonBar, evt.getKeyCode());
}//GEN-LAST:event_jpButtonBarKeyPressed

    @Action
    public void save()
    {
        saveSettings();
    }

    @Action
    /**
     * Tutorial: http://www.anyexample.com/programming/java/java_full_screen_image_viewer.xml
     */
    public void makeFullScreen()
    {
        if (activeNode != null)
        {
            String fileName = ((Page)activeNode.getUserObject()).filename;
            FullScreenView fsv = new FullScreenView(activeImage, this);
            fsv.setVisible(true);
        }
    }

    @Action
    public void handleItemEditClick()
    {
        DefaultMutableTreeNode node = getNodeForTreePath(jtLibraryTree.getSelectionPath());
        if (node != null)
        {
            if (node.getUserObject() instanceof Chapter)
            {
                editChapter(node);
            }
            else if (node.getUserObject() instanceof Manga)
            {
                editManga(node);
            }
        }
    }

    @Action
    public void deleteSelectedItem()
    {
        DefaultMutableTreeNode selectedNode = getNodeForTreePath(jtLibraryTree.getSelectionPath());
        if (selectedNode != null)
        {
            if (JOptionPane.showConfirmDialog(this.getFrame(), "Really delete " + selectedNode.toString() + "?", "Delete '" + selectedNode.toString() + "'?", JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION)
            {
                if (selectedNode.getUserObject() instanceof Chapter)
                {
                    SQLibrarian sql = new SQLibrarian();
                    if (sql.deleteChapterFromLibrary((Chapter)selectedNode.getUserObject()))
                    {
                        sql.closeConn();
                        Volume v = (Volume)((DefaultMutableTreeNode)selectedNode.getParent()).getUserObject();
                        v.chapters.remove((Chapter)selectedNode.getUserObject());
                    }
                }
                else if (selectedNode.getUserObject() instanceof Manga)
                {
                    SQLibrarian sql = new SQLibrarian();
                    if (sql.deleteMangaFromLibrary((Manga)selectedNode.getUserObject()))
                    {
                        sql.closeConn();
                        library.remove((Manga)selectedNode.getUserObject());
                    }
                }
                else if (selectedNode.getUserObject() instanceof Volume)
                {
                    SQLibrarian sql = new SQLibrarian();
                    if (sql.deleteVolumeFromLibrary((Volume)selectedNode.getUserObject()))
                    {
                        sql.closeConn();
                        Manga m = (Manga)((DefaultMutableTreeNode)selectedNode.getParent()).getUserObject();
                        m.volumes.remove((Volume)selectedNode.getUserObject());
                    }
                }
                else if (selectedNode.getUserObject() instanceof Page)
                {
                    SQLibrarian sql = new SQLibrarian();
                    if (sql.deletePageFromLibrary((Page)selectedNode.getUserObject()))
                    {
                        sql.closeConn();
                        Chapter c = (Chapter)((DefaultMutableTreeNode)selectedNode.getParent()).getUserObject();
                        c.pages.remove((Page)selectedNode.getUserObject());
                    }
                }
                displayMangaLibrary();
            }
        }
    }

    @Action
    public void importLibrary()
    {
        JFileChooser importChooser = new JFileChooser();
        importChooser.setFileFilter(new javax.swing.filechooser.FileFilter()
        {

            @Override
            public boolean accept(File f)
            {
                if (f.isFile())
                {
                    return f.getName().endsWith(".dat");
                }
                return false;
            }

            @Override
            public String getDescription()
            {
                return "Azure Data Files (*.dat)";
            }
        });
        importChooser.setApproveButtonText("Import");
        if (importChooser.showOpenDialog(this.getRootPane()) == JFileChooser.APPROVE_OPTION)
        {
            try
            {
                File f = importChooser.getSelectedFile();
                ObjectInputStream in = new ObjectInputStream(new FileInputStream(f));
                String version = in.readUTF();
                org.jdesktop.application.ResourceMap resourceMap = org.jdesktop.application.Application.getInstance(azure.Main.class).getContext().getResourceMap(MainAboutBox.class);


                if (!version.equals(resourceMap.getString("Application.version")))
                {
                    JOptionPane.showMessageDialog(this.getRootPane(), "Warning: This file was created with a different version of Azure.\nIt may not import correctly.", "Non-matching Versions", JOptionPane.WARNING_MESSAGE);
                }
                library = (ArrayList<Manga>)in.readObject();

                in.close();
            }
            catch (IOException io)
            {
                System.err.println(io.toString());
            }
            catch (ClassNotFoundException cnf)
            {
                System.err.println(cnf.toString());
            }
        }
        displayMangaLibrary();
    }

    @Action
    public void exportLibrary()
    {
        if (library.size() > 0)
        {
            JFileChooser exportChooser = new JFileChooser();
            exportChooser.setFileFilter(new javax.swing.filechooser.FileFilter()
            {

                @Override
                public boolean accept(File f)
                {
                    if (f.isFile())
                    {
                        return f.getName().endsWith(".dat");
                    }
                    return false;
                }

                @Override
                public String getDescription()
                {
                    return "Azure Data Files (*.dat)";
                }
            });
            exportChooser.setApproveButtonText("Export");
            exportChooser.setSelectedFile(new File("library.dat"));
            if (exportChooser.showSaveDialog(this.getRootPane()) == JFileChooser.APPROVE_OPTION)
            {
                try
                {
                    ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(exportChooser.getSelectedFile()));
                    org.jdesktop.application.ResourceMap resourceMap = org.jdesktop.application.Application.getInstance(azure.Main.class).getContext().getResourceMap(MainAboutBox.class);
                    out.writeUTF(resourceMap.getString("Application.version"));
                    out.writeObject(library);

                    out.flush();

                    out.close();
                }
                catch (IOException io)
                {
                    System.err.println(io.toString());
                }
            }
        }
        else
        {
            JOptionPane.showMessageDialog(this.getFrame(), "Azure's library is empty!", "Cannot Export Library", JOptionPane.INFORMATION_MESSAGE);
        }
    }
    // <editor-fold defaultstate="collapsed" desc="Variables">
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JMenuItem exportMenuItem;
    private javax.swing.JMenuItem importMenuItem;
    private javax.swing.JMenuItem jMenuItem1;
    private javax.swing.JMenuItem jMenuItem10;
    private javax.swing.JMenuItem jMenuItem3;
    private javax.swing.JMenuItem jMenuItem4;
    private javax.swing.JMenuItem jMenuItem5;
    private javax.swing.JMenuItem jMenuItem6;
    private javax.swing.JMenuItem jMenuItem7;
    private javax.swing.JMenuItem jMenuItem8;
    private javax.swing.JMenuItem jMenuItem9;
    private javax.swing.JPanel jPanel1;
    private javax.swing.JPopupMenu.Separator jSeparator1;
    private javax.swing.JPopupMenu.Separator jSeparator2;
    private javax.swing.JPopupMenu.Separator jSeparator3;
    private javax.swing.JPopupMenu.Separator jSeparator4;
    private javax.swing.JPopupMenu.Separator jSeparator5;
    private javax.swing.JButton jbAppendChapter;
    private javax.swing.JButton jbAppendVolume;
    private javax.swing.JButton jbFirst;
    private javax.swing.JButton jbFullScreen;
    private javax.swing.JButton jbImport;
    private javax.swing.JButton jbLast;
    private javax.swing.JButton jbNext;
    private javax.swing.JButton jbPrevious;
    private javax.swing.JButton jbResume;
    private javax.swing.JMenu jcmAppend;
    private javax.swing.JMenuItem jcmeAppendChapter;
    private javax.swing.JMenuItem jcmeAppendVolume;
    private javax.swing.JMenuItem jcmeDeleteItem;
    private javax.swing.JMenuItem jcmeEditItem;
    private javax.swing.JFormattedTextField jftfPageNumber;
    private javax.swing.JLabel jlImage;
    private javax.swing.JLabel jlIsNotANumber;
    private javax.swing.JLabel jlPage;
    private javax.swing.JLabel jlTitle;
    private javax.swing.JPanel jpButtonBar;
    private javax.swing.JPanel jpImage;
    private javax.swing.JPanel jpLibrary;
    private javax.swing.JPanel jpLibraryButtonBar;
    private javax.swing.JPanel jpViewer;
    private javax.swing.JPopupMenu jpmTreeContextMenu;
    private javax.swing.JScrollPane jspLibrary;
    private javax.swing.JTree jtLibraryTree;
    private javax.swing.JTabbedPane jtpViewSwitch;
    private javax.swing.JPanel mainPanel;
    private javax.swing.JMenuBar menuBar;
    private javax.swing.JMenu navigateMenu;
    private javax.swing.JMenuItem saveMenuItem;
    private javax.swing.JMenu toolsMenu;
    // End of variables declaration//GEN-END:variables
    //</editor-fold>
    private JDialog aboutBox;
}
